﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Text;

namespace Wild2.Models.Captcha
{
    [Serializable]
    public class Captcha
    {
        string _text;
        DateTime _date;

        public Captcha(string t)
        {
            this._text = t;
            this._date = DateTime.Now;
        }

        public Captcha(string t, DateTime dt)
        {
            this._text = t;
            this._date = dt;
        }

        public DateTime date
        {
            get
            {
                return this._date;
            }
            //set
            //{
            //    this._backgroundNoise = value;
            //}
        }
        public string text
        {
            get
            {
                return this._text;
            }
            //set
            //{
            //    this._backgroundNoise = value;
            //}
        }
    }

    public class CaptchaImage
    {
        #region Properties

        private int _height = 35;
        private int _width = 100;
        private Random _rand = new Random();
        private DateTime _generatedAt;
        private string _randomText;
        private int _randomTextLength = 3;
        //private string _randomTextChars = "weruioaszxcvnm";
        private string _randomTextChars = "1234567890";
        private string _fontFamilyName = "";
        private FontWarpFactor _fontWarp = FontWarpFactor.Medium;
        private BackgroundNoiseLevel _backgroundNoise = BackgroundNoiseLevel.Low;
        private LineNoiseLevel _lineNoise = LineNoiseLevel.Low;
        private string _guid;
        private string _fontWhitelist = "arial;arial black;comic sans ms;estrangelo edessa;franklin gothic medium;georgia;lucida console;lucida sans unicode;mangal;microsoft sans serif;palatino linotype;sylfaen;tahoma;times new roman;trebuchet ms;verdana";

        public CaptchaImage()
        {
            this._randomText = this.GenerateRandomText();
            this._generatedAt = DateTime.Now;
            this._guid = Guid.NewGuid().ToString();
            //CaptchaText ct = new CaptchaText(_randomText);
            //HttpContext.Current.Session.Add("RegCode", ct);
        }
        #endregion

        #region Public Properties

        /// <summary>
        /// Amount of background noise to apply to the Captcha image.
        /// </summary>
        public BackgroundNoiseLevel BackgroundNoise
        {
            get
            {
                return this._backgroundNoise;
            }
            set
            {
                this._backgroundNoise = value;
            }
        }

        /// <summary>
        /// Font family to use when drawing the Captcha text. If no font is provided, a random font will be chosen from the font whitelist for each character.
        /// </summary>
        public string Font
        {
            get
            {
                return this._fontFamilyName;
            }
            set
            {
                try
                {
                    Font font1 = new Font(value, 12f);
                    _fontFamilyName = value;
                    font1.Dispose();
                }
                catch (Exception)
                {
                    _fontFamilyName = FontFamily.GenericSerif.Name;
                }
            }
        }

        /// <summary>
        /// Amount of random warping to apply to the Captcha text.
        /// </summary>
        public FontWarpFactor FontWarp
        {
            get
            {
                return this._fontWarp;
            }
            set
            {
                this._fontWarp = value;
            }
        }

        /// <summary>
        /// A semicolon-delimited list of valid fonts to use when no font is provided.
        /// </summary>
        public string FontWhitelist
        {
            get
            {
                return this._fontWhitelist;
            }
            set
            {
                this._fontWhitelist = value;
            }
        }

        /// <summary>
        /// Height of Captcha image to generate, in pixels 
        /// </summary>
        public int Height
        {
            get
            {
                return _height;
            }
            set
            {
                if (value <= 30)
                {
                    throw new ArgumentOutOfRangeException("height", value, "height must be greater than 30.");
                }
                _height = value;
            }
        }

        /// <summary>
        /// Кривизна линии
        /// </summary>
        public LineNoiseLevel LineNoise
        {
            get
            {
                return _lineNoise;
            }
            set
            {
                _lineNoise = value;
            }
        }

        /// <summary>
        /// Returns the date and time this image was last rendered
        /// </summary>
        public DateTime RenderedAt
        {
            get
            {
                return this._generatedAt;
            }
        }

        /// <summary>
        /// Returns the randomly generated Captcha text.
        /// </summary>
        public string Text
        {
            get
            {
                return this._randomText;
            }
        }

        /// <summary>
        /// A string of valid characters to use in the Captcha text. 
        /// A random character will be selected from this string for each character.
        /// </summary>
        public string TextChars
        {
            get
            {
                return this._randomTextChars;
            }
            set
            {
                this._randomTextChars = value;
                this._randomText = this.GenerateRandomText();
            }
        }

        /// <summary>
        /// Number of characters to use in the Captcha text. 
        /// </summary>
        public int TextLength
        {
            get
            {
                return this._randomTextLength;
            }
            set
            {
                this._randomTextLength = value;
                this._randomText = this.GenerateRandomText();
            }
        }

        /// <summary>
        /// Returns a GUID that uniquely identifies this Captcha
        /// </summary>
        public string UniqueId
        {
            get
            {
                return this._guid;
            }
        }

        /// <summary>
        /// Width of Captcha image to generate, in pixels 
        /// </summary>
        public int Width
        {
            get
            {
                return _width;
            }
            set
            {
                if (value <= 60)
                {
                    throw new ArgumentOutOfRangeException("width", value, "width must be greater than 60.");
                }
                _width = value;
            }
        }

        #endregion

        #region Public Enumerations
        /// <summary>
        /// Amount of background noise to add to rendered image
        /// </summary>
        public enum BackgroundNoiseLevel
        {
            None,
            Low,
            Medium,
            High,
            Extreme
        }

        /// <summary>
        /// Amount of random font warping to apply to rendered text
        /// </summary>
        public enum FontWarpFactor
        {
            None,
            Low,
            Medium,
            High,
            Extreme
        }

        /// <summary>
        /// Amount of curved line noise to add to rendered image
        /// </summary>
        public enum LineNoiseLevel
        {
            None,
            Low,
            Medium,
            High,
            Extreme
        }
        #endregion

        /// <summary>
        /// Forces a new Captcha image to be generated using current property value settings.
        /// </summary>
        public Bitmap RenderImage()
        {
            return this.GenerateImagePrivate();
        }

        /// <summary>
        /// Returns a random font family from the font whitelist
        /// </summary>
        private string RandomFontFamily()
        {
            string[] ff = _fontWhitelist.Split(';');
            int r = _rand.Next(0, ff.Length);
            string font = Convert.ToString(ff.GetValue(r));
            return font;
        }

        /// <summary>
        /// generate random text for the CAPTCHA
        /// </summary>
        private string GenerateRandomText()
        {
            StringBuilder sb = new StringBuilder(_randomTextLength);
            int maxLength = _randomTextChars.Length;
            int l = _randomTextLength - 1;

            for (int n = 0; n <= l; n++)
            {
                sb.Append(_randomTextChars.Substring(_rand.Next(maxLength), 1));
            }

            return sb.ToString();
        }

        /// <summary>
        /// Возвращает случайные координаты точек в определеннго прямоугольника
        /// принимает параметры xmin/xmax и ymin/ymax и возвращает координаты PointF
        /// </summary>    
        private PointF RandomPoint(int xmin, int xmax, ref int ymin, ref int ymax)
        {
            return new PointF(_rand.Next(xmin, xmax), _rand.Next(ymin, ymax));
        }

        /// <summary>
        /// Возвращает случайные координаты точек в определенных диапазоне x y
        /// </summary>    
        private PointF RandomPoint(Rectangle rect)
        {
            int rectTop = rect.Top;
            int rectBottom = rect.Bottom;
            return RandomPoint(rect.Left, rect.Width, ref rectTop, ref rectBottom);
        }

        /// <summary>
        /// Возвращает GraphicsPath содержащий указанную строку и шрифт
        /// </summary>  
        private GraphicsPath TextPath(string s, Font f, Rectangle r)
        {
            StringFormat sf = new StringFormat();
            sf.Alignment = StringAlignment.Near;
            sf.LineAlignment = StringAlignment.Near;
            GraphicsPath gp = new GraphicsPath();
            gp.AddString(s, f.FontFamily, (int)f.Style, f.Size, r, sf);
            return gp;
        }

        /// <summary>
        /// Returns the CAPTCHA font in an appropriate size 
        /// </summary>
        private Font GetFont()
        {
            //possible values: 0.7, 0.8, 0.85, 0.9, 0.95
            float font_size;
            string font_name = _fontFamilyName;
            if (font_name == "")
            {
                font_name = RandomFontFamily();
            }
            switch (FontWarp)
            {
                case FontWarpFactor.None:
                    font_size = Convert.ToInt32(_height * 0.7);
                    break;

                case FontWarpFactor.Low:
                    font_size = Convert.ToInt32(_height * 0.8);
                    break;

                case FontWarpFactor.Medium:
                    font_size = Convert.ToInt32(_height * 0.85);
                    break;

                case FontWarpFactor.High:
                    font_size = Convert.ToInt32(_height * 0.9);
                    break;

                default:
                    font_size = Convert.ToInt32(_height * 0.7);
                    break;
            }

            return new Font(font_name, font_size, FontStyle.Regular);
        }

        /// <summary>
        /// Проводим рендеринг изображения
        /// </summary>
        private Bitmap GenerateImagePrivate()
        {
            Font fnt = null;
            Bitmap bmp = new Bitmap(_width, _height, PixelFormat.Format32bppArgb);
            Graphics gr = Graphics.FromImage(bmp);
            gr.SmoothingMode = SmoothingMode.AntiAlias;
            //заполняем пустой белый прямоугольник
            Rectangle rect = new Rectangle(0, 0, _width, _height);
            Brush br = new SolidBrush(Color.White);
            gr.FillRectangle(br, rect);

            int charOffset = 0;
            double charWidth = _width / _randomTextLength;
            Rectangle rectChar;

            foreach (char c in _randomText)
            {
                fnt = GetFont();
                rectChar = new Rectangle(Convert.ToInt32(charOffset * charWidth), 0, Convert.ToInt32(charWidth), _height);
                GraphicsPath gp = TextPath(Convert.ToString(c), fnt, rectChar);
                WarpText(gp, rectChar);
                Color rc = Color.Black;
                //Color rc = GetRandomColor();
                br = new SolidBrush(rc);
                gr.FillPath(br, gp);
                charOffset++;
            }

            AddNoise(gr, rect);
            AddLine(gr, rect);
            fnt.Dispose();
            br.Dispose();
            gr.Dispose();
            return bmp;
        }

        /// <summary>
        /// Warp the provided text GraphicsPath by a variable amount
        /// </summary>
        private void WarpText(GraphicsPath textPath, Rectangle rect)
        {

            //possible values: 0, 1, 1.3, 1.4, 1.5
            float RangeModifier;
            //possible values: 0, 6, 5, 4.5, 4
            float WarpDivisor;

            switch (_fontWarp)
            {
                case FontWarpFactor.None:
                    return;

                case FontWarpFactor.Low:
                    WarpDivisor = 6f;
                    RangeModifier = 1f;
                    break;

                case FontWarpFactor.Medium:
                    WarpDivisor = 5f;
                    RangeModifier = 1.3f;
                    break;

                case FontWarpFactor.High:
                    WarpDivisor = 4.5f;
                    RangeModifier = 1.4f;
                    break;

                case FontWarpFactor.Extreme:
                    WarpDivisor = 4f;
                    RangeModifier = 1.5f;
                    break;

                default:
                    WarpDivisor = 6f;
                    RangeModifier = 1f;
                    break;

            }


            RectangleF rectF = new RectangleF(Convert.ToSingle(rect.Left), 0, Convert.ToSingle(rect.Width), rect.Height);
            int hrange = Convert.ToInt32(rect.Height / WarpDivisor);
            int wrange = Convert.ToInt32(rect.Width / WarpDivisor);
            int left = rect.Left - Convert.ToInt32(wrange * RangeModifier);
            int top = rect.Top - Convert.ToInt32(hrange * RangeModifier);
            int width = (rect.Left + rect.Width) + Convert.ToInt32(wrange * RangeModifier);
            int height = (rect.Top + rect.Height) + Convert.ToInt32(hrange * RangeModifier);
            if (left < 0)
            {
                left = 0;
            }
            if (top < 0)
            {
                top = 0;
            }
            if (width > Width)
            {
                width = Width;
            }
            if (height > Height)
            {
                height = Height;
            }
            int tophrange = top + hrange;
            int heighthrange = height - hrange;

            PointF leftTop = RandomPoint(left, left + wrange, ref top, ref tophrange);
            PointF rightTop = RandomPoint(width - wrange, width, ref top, ref tophrange);
            PointF leftBottom = RandomPoint(left, left + wrange, ref heighthrange, ref height);
            PointF rightBottom = RandomPoint(width - wrange, width, ref heighthrange, ref height);



            PointF[] points = new PointF[] { leftTop, rightTop, leftBottom, rightBottom };
            Matrix m = new Matrix();
            m.Translate(0, 0);
            textPath.Warp(points, rectF, m, WarpMode.Perspective, 0);
        }


        /// <summary>
        /// Добавляет переменный уровень графического шума к изображению
        /// </summary>   
        private void AddNoise(Graphics graphics1, Rectangle rect)
        {
            //possible values: 0, 30, 18, 16, 12
            int density;
            //possible values: 0, 40, 40, 39, 38
            int size;
            switch (_backgroundNoise)
            {
                case BackgroundNoiseLevel.None:
                    return;

                case BackgroundNoiseLevel.Low:
                    density = 30;
                    size = 40;
                    break;

                case BackgroundNoiseLevel.Medium:
                    density = 18;
                    size = 40;
                    break;

                case BackgroundNoiseLevel.High:
                    density = 16;
                    size = 39;
                    break;

                case BackgroundNoiseLevel.Extreme:
                    density = 12;
                    size = 38;
                    break;

                default:
                    density = 30;
                    size = 40;
                    break;
            }
            Color cl = Color.Black;
            //Color cl = GetRandomColor();
            SolidBrush br = new SolidBrush(cl);
            int max = Convert.ToInt32(Math.Max(rect.Width, rect.Height) / size);

            int s = Convert.ToInt32((rect.Width * rect.Height) / density);

            for (int i = 0; i <= s; i++)
            {
                graphics1.FillEllipse(br, _rand.Next(rect.Width), _rand.Next(rect.Height), _rand.Next(max), _rand.Next(max));
            }
            br.Dispose();
        }


        /// <summary>
        /// Добавляет переменный уровень изогнутых линий к изображению
        /// </summary>    
        private void AddLine(Graphics graphics1, Rectangle rect)
        {
            //possible values: 0, 4, 5, 3, 3
            int length;
            //possible values: 0, 1, 1, 2, 3
            int linecount;
            //possible values: 0, 31.25, 27.7777, 25, 22.7272
            float width;
            switch (_lineNoise)
            {
                case LineNoiseLevel.None:
                    return;

                case LineNoiseLevel.Low:
                    length = 4;
                    width = Convert.ToSingle(_height / 31.25);
                    linecount = 1;
                    break;

                case LineNoiseLevel.Medium:
                    length = 5;
                    width = Convert.ToSingle(_height / 27.7777);
                    linecount = 1;
                    break;

                case LineNoiseLevel.High:
                    length = 3;
                    width = Convert.ToSingle(_height / 25.0);
                    linecount = 2;
                    break;

                case LineNoiseLevel.Extreme:
                    length = 3;
                    width = Convert.ToSingle(_height / 22.7272);
                    linecount = 3;
                    break;
                default:
                    length = 4;
                    width = Convert.ToSingle(_height / 31.25);
                    linecount = 1;
                    break;
            }
            PointF[] pf = new PointF[length + 1];
            Color cp = Color.Black;
            //Color cp = GetRandomColor();
            Pen p = new Pen(cp, width);
            //Pen p = new Pen(Color.Green, width);


            for (int l = 1; l <= linecount; l++)
            {

                for (int i = 0; i <= length; i++)
                {
                    pf[i] = RandomPoint(rect);
                }
                graphics1.DrawCurve(p, pf, 1.75f);
            }
            p.Dispose();
        }

        //private Array GetColorArray()
        //{
        //    Color someColor = Color.FromArgb(0);
        //    KnownColor[] colorMatches = new KnownColor[166];
        //    int count = 0;
        //    for (KnownColor knownColor = 0; knownColor < KnownColor.Yellow; knownColor++)
        //    {
        //        someColor = Color.FromKnownColor(knownColor);
        //        colorMatches[count++] = knownColor;
        //    }
        //    return colorMatches;
        //}

        //private Color GetRandomColor()
        //{
        //    Array cm = null;
        //    string key = "Colors";
        //    if (HttpRuntime.Cache[key] != null)
        //    {
        //        cm = (Array)HttpRuntime.Cache[key];
        //    }
        //    else
        //    {
        //        cm = GetColorArray();
        //        HttpRuntime.Cache.Insert(key, cm);
        //    }
        //    int cnt = _rand.Next(3, cm.Length);
        //    string clr = Convert.ToString(cm.GetValue(cnt));
        //    Color nc = Color.FromName(clr);
        //    return nc;
        //}
    }
}